﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using System.Windows.Browser;
using Radovici.SharedLibrary;
using YouTubePlaylist.MusicVideoService;

namespace YouTubePlaylist.Models
{
    public class DataModel : Notifiable
    {
        public const string EndPoint = "http://localhost:58392/";

        #region Static functionality

        #region Static fields

        private static readonly string _appName;
        private static readonly DataModel _sharedSingleton = new DataModel();

        #endregion

        static DataModel()
        {
            _appName = HtmlPage.Document.DocumentUri.Host;
        }

        public static DataModel Singleton
        {
            get { return _sharedSingleton; }
        }

        private static IDictionary<string, string> QueryString
        {
            get { return HtmlPage.Document.QueryString; }
        }

        public static Dictionary<string, string> DefaultPreferences
        {
            get
            {
                var preferences = new Dictionary<string, string>();
                BrowserInformation info = HtmlPage.BrowserInformation;
                preferences["BrowserInformation"] = string.Format("{0},{1},{2},{3},{4}", info.UserAgent, info.Name,
                                                                  info.BrowserVersion, info.CookiesEnabled,
                                                                  info.Platform);
                preferences["AppName"] = _appName;
                return preferences;
            }
        }

        private static User DefaultUser
        {
            get
            {
                var u = new User
                            {
                                Email = Cookies.Load("email") ?? "demo",
                                Password = Cookies.Load("password")
                            }; //TODO: Serialize objects to cookies.
                return u;
            }
        }

        #endregion

        #region Private fields

        private readonly Dictionary<int, VideoPlaylist> _allPlaylists = new Dictionary<int, VideoPlaylist>();

        private readonly MusicVideoServiceClient _client =
            new MusicVideoServiceClient(
                new BasicHttpBinding {MaxBufferSize = 2147483647, MaxReceivedMessageSize = 2147483647},
                new EndpointAddress(EndPoint + "MusicVideoService.svc"));

        private readonly Timer _refreshTimer;
        //Status
        private readonly List<string> _statusBarMessages = new List<string>();
        private VideoPlaylist _linkedPlaylist;
        private User _user;

        #region Playlists

        private readonly ObservableCollection<VideoPlaylist> _foundPlaylists = new ObservableCollection<VideoPlaylist>();

        private readonly ObservableCollection<VideoPlaylist> _mostRecentlyCreatedPlaylists =
            new ObservableCollection<VideoPlaylist>();

        private readonly ObservableCollection<VideoPlaylist> _mostRecentlyUpdatedPlaylists =
            new ObservableCollection<VideoPlaylist>();

        private readonly ObservableCollection<VideoPlaylist> _myPlaylists = new ObservableCollection<VideoPlaylist>();

        private readonly ObservableCollection<VideoPlaylist> _playingPlaylists =
            new ObservableCollection<VideoPlaylist>();

        #endregion

        #endregion

        //public const string EndPoint = "http://play.youballer.com/";

        private DataModel()
        {
            InitializeUser();
            MonitorPlaylists();
            RegisterServiceCallbacks();
            _refreshTimer = new Timer(OnRefreshTimerCallBack, null, int.MaxValue, int.MaxValue);
            if (QueryString != null && QueryString.Count != 0 && QueryString.ContainsKey("playlist"))
            {
                _client.GetPlaylistAsync(User, Convert.ToInt32(QueryString["playlist"]),
                                         new AsyncState(this, _playingPlaylists, "Getting Linked Playlist"));
            }
        }

        #region User functionality

        public User User
        {
            get { return _user; }
        }

        private void OnUserChanged(object sender, PropertyChangedEventArgs e)
        {
            GetUser();
        }

        public void UpdatePreferences()
        {
            _client.UpdatePreferencesAsync(_user);
        }

        private void OnPlayingPlaylistsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            _user.Preferences["Playing"] = _playingPlaylists.Join(item => item.Id.ToString());
            UpdatePreferences();
        }

        private void InitializeUser()
        {
            _user = DefaultUser;
            Context.Post(delegate { GetUser(); }, null);
        }

        #endregion

        public ObservableCollection<VideoPlaylist> PlayingPlaylists
        {
            get { return _playingPlaylists; }
        }

        public ObservableCollection<VideoPlaylist> MyPlaylists
        {
            get { return _myPlaylists; }
        }

        public ObservableCollection<VideoPlaylist> FoundPlaylists
        {
            get { return _foundPlaylists; }
        }

        public ObservableCollection<VideoPlaylist> MostRecentlyCreatedPlaylists
        {
            get { return _mostRecentlyCreatedPlaylists; }
        }

        public ObservableCollection<VideoPlaylist> MostRecentlyUpdatedPlaylists
        {
            get { return _mostRecentlyUpdatedPlaylists; }
        }

        public VideoPlaylist LinkedPlaylist
        {
            get
            {
                return _linkedPlaylist;
                //if (QueryString != null && QueryString.ContainsKey("playlist"))
                //{
                //    return Convert.ToInt32(QueryString["playlist"]);
                //}
                //else if (_user.Preferences.ContainsKey("CurrentPlaylist"))
                //{
                //    return Convert.ToInt32(_user.Preferences["CurrentPlaylist"]);
                //}
                //return 0;
            }
        }

        public VideoTrack LinkedTrack
        {
            get
            {
                if (_linkedPlaylist != null)
                {
                    if (QueryString != null && QueryString.ContainsKey("track"))
                    {
                        return _linkedPlaylist.Tracks.SingleOrDefault(t => t.Id == Convert.ToInt32(QueryString["track"]));
                    }
                    if (_user.Preferences.ContainsKey("CurrentTrack"))
                    {
                        return _linkedPlaylist.Tracks.SingleOrDefault(t => t.Id == Convert.ToInt32(_user.Preferences["CurrentTrack"]));
                    }
                }
                return null;
            }
        }

        public bool IsCalculating
        {
            get
            {
                lock (_statusBarMessages)
                {
                    return _statusBarMessages.Count > 0;
                }
            }
        }

        public string Status
        {
            get
            {
                string status = string.Empty;
                lock (_statusBarMessages)
                {
                    _statusBarMessages.ForEach(s => status += s + ", ");
                }
                if (status.EndsWith(", "))
                {
                    status = status.Substring(0, status.Length - ", ".Length);
                }
                return status;
            }
        }

        public bool CanUserAdd(ObservableCollection<VideoPlaylist> playlists)
        {
            return (playlists == _myPlaylists);
        }

        public bool CanUserDelete(ObservableCollection<VideoPlaylist> playlists)
        {
            return (playlists == _myPlaylists || playlists == _playingPlaylists);
        }

        public bool CanUserEdit(ObservableCollection<VideoPlaylist> playlists)
        {
            return CanUserAdd(playlists);
        }

        public void DeletePlaylist(VideoPlaylist playlist, ObservableCollection<VideoPlaylist> playlists)
        {
            if (playlists == _myPlaylists)
            {
                if (_myPlaylists.Contains(playlist))
                {
                    _myPlaylists.Remove(playlist);
                    _client.DeletePlaylistAsync(_user, playlist.Id);
                }
            }
            else
            {
                playlists.Remove(playlist);
            }
        }

        public void DeleteTrack(VideoPlaylist playlist, VideoTrack track)
        {
            if (_myPlaylists.Contains(playlist))
            {
                playlist.Tracks.Remove(track);
                _client.DeleteTrackFromPlaylistAsync(_user, playlist.Id, track.Id);
            }
        }

        #region SaveTrackUrl

        public void SaveTrackUrl(VideoTrack track)
        {
            _client.SaveTrackUrlAsync(User, track.Id, track.SelectedUrl.Id,
                                      new AsyncState(this, null, "Setting Track's Default Video"));
        }

        private void OnSaveTrackUrlCompleted(object sender, SaveTrackUrlCompletedEventArgs e)
        {
            VideoTrack t = e.Result;
            if (t != null)
            {
                UpdateTrack(t);
            }
        }

        #endregion

        #region RefreshTrack

        public void RefreshTrack(VideoTrack track)
        {
            _client.RefreshTrackAsync(User, track.Id, new AsyncState(this, null, "Refresh Track's Videos"));
        }

        private void onRefreshTrackCompleted(object sender, RefreshTrackCompletedEventArgs e)
        {
            VideoTrack t = e.Result;
            if (t != null)
            {
                UpdateTrack(t);
            }
        }

        #endregion

        #region GetUser

        public void GetUser()
        {
            _client.GetUserAsync(User, _user.Email, _user.Password, new AsyncState(this, null, "Getting User"));
        }

        private void OnGetUserCompleted(object sender, GetUserCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                var state = e.UserState as AsyncState;
                if (state != null)
                {
                    using (state)
                    {
                        _user = e.Result;
                        _user.PropertyChanged += OnUserChanged;
                        Notify("User");
                        //Initialize user's (i.e., my) playlists
                        RefreshPlaylists();

                        if (_user.Preferences.ContainsKey("Playing"))
                        {
                            string nowPlaying = _user.Preferences["Playing"];
                            if (!string.IsNullOrEmpty(nowPlaying))
                            {
                                QueuePlaylists(nowPlaying.Split(',', s => int.Parse(s)));
                            }
                        }

                        Cookies.Save("email", _user.Email);
                        if ((QueryString == null || QueryString.Count == 0) &&
                            _user.Preferences.ContainsKey("CurrentPlaylist"))
                        {
                            _client.GetPlaylistAsync(User, Convert.ToInt32(_user.Preferences["CurrentPlaylist"]),
                                                     new AsyncState(this, _playingPlaylists, "Getting Last Track"));
                        }
                        _refreshTimer.Change(0, 600000);
                    }
                }
            }
        }

        #endregion

        #region Update calls

        public void QueuePlaylist(VideoPlaylist p)
        {
            UpdatePlaylists(_playingPlaylists, p);
        }

        public void QueuePlaylists(IEnumerable<int> playlistIds)
        {
            _client.GetPlaylistsFromIdsAsync(_user, playlistIds.ToObservableCollection(),
                                             new AsyncState(this, _playingPlaylists,
                                                            string.Format("Getting Select Playlists")));
        }

        public void FindPlaylists(string search)
        {
            search = search.Trim();
            if (!string.IsNullOrEmpty(search))
            {
                _client.GetPlaylistsFromSearchAsync(_user, search,
                                                    new AsyncState(this, _foundPlaylists,
                                                                   string.Format("Finding Playlists with '{0}'", search)));
            }
        }

        public void AddPlaylist(VideoPlaylist playlist)
        {
            AddPlaylist(playlist, _myPlaylists);
        }

        public void AddPlaylist(VideoPlaylist playlist, ObservableCollection<VideoPlaylist> playlists)
        {
            if (playlists == MyPlaylists)
            {
                _client.AddPlaylistAsync(_user, playlist, new AsyncState(this, _myPlaylists, "Adding Playlist"));
            }
            else //if (playlists == PlayingPlaylists)
            {
                playlists.Add(playlist);
            }
        }

        public void RefreshPlaylists()
        {
            _myPlaylists.Clear();
                //TODO: Cache old _myPlaylists and compare to ensure atomic operation (or appearence of an atomic op).
            _client.GetPlaylistsAsync(_user, new AsyncState(this, _myPlaylists, "Getting Playlists"));
        }

        public void AddPlaylists(ObservableCollection<VideoPlaylist> playlists)
        {
            _client.AddPlaylistsAsync(_user, playlists, new AsyncState(this, _myPlaylists, "Adding Playlists"));
        }

        public void AddTrack(VideoPlaylist playlist, VideoTrack track)
        {
            _client.AddTrackAsync(_user, playlist.Id, track, new AsyncState(this, _myPlaylists, "Adding Track"));
        }

        public void AddTracks(VideoPlaylist playlist, IEnumerable<VideoTrack> tracks)
        {
            var tmpTracks = new ObservableCollection<VideoTrack>();
            foreach (VideoTrack track in tracks)
            {
                tmpTracks.Add(track);
            }
            _client.AddTracksAsync(_user, playlist.Id, tmpTracks, new AsyncState(this, _myPlaylists, "Adding Tracks"));
        }

        public void CopyPlaylist(VideoPlaylist playlist)
        {
            _client.CopyPlaylistAsync(_user, playlist.Id, new AsyncState(this, _myPlaylists, "Copying Playlist"));
        }

        #endregion

        #region Update callbacks

        private void OnGetPlaylistCompleted(object sender, GetPlaylistCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, new ObservableCollection<VideoPlaylist> {e.Result});
            _linkedPlaylist = _allPlaylists[e.Result.Id];
            Notify("LinkedTrack");
        }

        private void OnAddTrackCompleted(object sender, AddTrackCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, new ObservableCollection<VideoPlaylist> {e.Result});
        }

        private void OnAddTracksCompleted(object sender, AddTracksCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, new ObservableCollection<VideoPlaylist> {e.Result});
        }

        private void OnCopyPlaylistCompleted(object sender, CopyPlaylistCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, new ObservableCollection<VideoPlaylist> {e.Result});
        }

        private void OnAddPlaylistCompleted(object sender, AddPlaylistCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, new ObservableCollection<VideoPlaylist> {e.Result});
        }

        private void OnAddPlaylistsCompleted(object sender, AddPlaylistsCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, e.Result);
        }

        private void OnGetPlaylistsFromSearchCompleted(object sender, GetPlaylistsFromSearchCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, e.Result);
        }

        private void OnGetPlaylistsFromIdsCompleted(object sender, GetPlaylistsFromIdsCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, e.Result);
        }

        private void OnGetPlaylistsCompleted(object sender, GetPlaylistsCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, e.Result);
        }

        private void OnGetMostRecentlyCreatedPlaylists(object sender, GetMostRecentlyCreatedPlaylistsCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, e.Result);
        }

        private void OnGetMostRecentlyUpdatedPlaylists(object sender, GetMostRecentlyUpdatedPlaylistsCompletedEventArgs e)
        {
            OnGetPlaylistsCompleted(e.UserState as AsyncState, e.Error, e.Result);
        }

        private void OnGetPlaylistsCompleted(AsyncState state, Exception error, ObservableCollection<VideoPlaylist> playlists)
        {
            if (state != null && error == null)
            {
                using (state)
                {
                    var curPlaylists = state.State as ObservableCollection<VideoPlaylist>;
                    if (curPlaylists == _myPlaylists)
                    {
                        UpdatePlaylists(curPlaylists, playlists);
                        Notify("MyPlaylists");
                    }
                    else if (curPlaylists == _mostRecentlyCreatedPlaylists)
                    {
                        UpdatePlaylists(curPlaylists, playlists);
                        if (_mostRecentlyCreatedPlaylists.Count > 5)
                        {
                            _mostRecentlyCreatedPlaylists.Clear();
                            UpdatePlaylists(curPlaylists, playlists);
                        }
                        Notify("MostRecentlyCreatedPlaylists");
                    }
                    else if (curPlaylists == _mostRecentlyUpdatedPlaylists)
                    {
                        UpdatePlaylists(curPlaylists, playlists);
                        if (_mostRecentlyUpdatedPlaylists.Count > 5)
                        {
                            _mostRecentlyUpdatedPlaylists.Clear();
                            UpdatePlaylists(curPlaylists, playlists);
                        }
                        Notify("MostRecentlyUpdatedPlaylists");
                    }
                    else if (curPlaylists == _playingPlaylists)
                    {
                        UpdatePlaylists(curPlaylists, playlists);
                        Notify("PlayingPlaylists");
                    }
                    else if (curPlaylists == _foundPlaylists)
                    {
                        _foundPlaylists.Clear();
                        UpdatePlaylists(curPlaylists, playlists);
                        Notify("FoundPlaylists");
                    }
                }
            }
        }

        #endregion

        #region Update mechanisms

        private void UpdatePlaylists(ObservableCollection<VideoPlaylist> tarPlaylists,
                                     IEnumerable<VideoPlaylist> playlists)
        {
            foreach (VideoPlaylist playlist in playlists)
            {
                UpdatePlaylists(tarPlaylists, playlist);
            }
        }

        private void UpdatePlaylists(ObservableCollection<VideoPlaylist> tarPlaylists, VideoPlaylist playlist)
        {
            playlist = UpdatePlaylist(playlist);
            VideoPlaylist exPlaylist = tarPlaylists.SingleOrDefault(p => p.Id == playlist.Id);
            if (exPlaylist == null)
            {
                Context.Send(delegate { tarPlaylists.Add(playlist); }, null);
            }
        }

        private VideoPlaylist UpdatePlaylist(VideoPlaylist playlist)
        {
            Context.Send(delegate
                             {
                                 if (_allPlaylists.ContainsKey(playlist.Id))
                                 {
                                     VideoPlaylist oldPlaylist = _allPlaylists[playlist.Id];
                                     oldPlaylist.Copy(playlist);
                                     playlist = oldPlaylist;
                                 }
                                 else
                                 {
                                     _allPlaylists[playlist.Id] = playlist;
                                 }
                             }, null);
            return playlist;
        }

        private void UpdateTrack(VideoTrack track)
        {
            //NOTE: Better algorithm; this is too manual. What if we add another playlist?
            UpdateTrack(_myPlaylists, track);
            UpdateTrack(_playingPlaylists, track);
            UpdateTrack(_mostRecentlyCreatedPlaylists, track);
            UpdateTrack(_mostRecentlyCreatedPlaylists, track);
            UpdateTrack(_foundPlaylists, track);
        }

        private void UpdateTrack(IEnumerable<VideoPlaylist> playlists, VideoTrack track)
        {
            foreach (VideoPlaylist playlist in playlists)
            {
                playlist.UpdateTrack(track);
            }
        }

        #endregion

        #region Status functionality

        private void AddStatus(object status)
        {
            var s = status as string;
            if (s != null)
            {
                lock (_statusBarMessages)
                {
                    _statusBarMessages.Add(s);
                }
                NotifyIsCalculating();
            }
        }

        private void RemoveStatus(object status)
        {
            var s = status as string;
            if (s != null)
            {
                lock (_statusBarMessages)
                {
                    _statusBarMessages.Remove(s);
                }
                NotifyIsCalculating();
            }
        }

        #endregion

        #region Private methods

        private void OnRefreshTimerCallBack(object state)
        {
            _client.GetMostRecentlyCreatedPlaylistsAsync(User, 5,
                                                         new AsyncState(this, _mostRecentlyCreatedPlaylists,
                                                                        "Getting Most Recent Playlists"));
            _client.GetMostRecentlyUpdatedPlaylistsAsync(User, 5,
                                                         new AsyncState(this, _mostRecentlyUpdatedPlaylists,
                                                                        "Getting Most Active Playlists"));
        }

        private void NotifyIsCalculating()
        {
            Notify("IsCalculating");
            Notify("Status");
        }

        private void MonitorPlaylists()
        {
            _playingPlaylists.CollectionChanged += OnPlayingPlaylistsChanged;
        }

        private void RegisterServiceCallbacks()
        {
            _client.AddPlaylistCompleted += OnAddPlaylistCompleted;
            _client.AddPlaylistsCompleted += OnAddPlaylistsCompleted;
            _client.AddTrackCompleted += OnAddTrackCompleted;
            _client.AddTracksCompleted += OnAddTracksCompleted;
            _client.CopyPlaylistCompleted += OnCopyPlaylistCompleted;
            _client.GetPlaylistCompleted += OnGetPlaylistCompleted;
            _client.GetPlaylistsCompleted += OnGetPlaylistsCompleted;
            _client.GetPlaylistsFromIdsCompleted += OnGetPlaylistsFromIdsCompleted;
            _client.GetPlaylistsFromSearchCompleted += OnGetPlaylistsFromSearchCompleted;
            _client.GetUserCompleted += OnGetUserCompleted;
            _client.GetMostRecentlyCreatedPlaylistsCompleted += OnGetMostRecentlyCreatedPlaylists;
            _client.GetMostRecentlyUpdatedPlaylistsCompleted += OnGetMostRecentlyUpdatedPlaylists;
            _client.RefreshTrackCompleted += onRefreshTrackCompleted;
            _client.SaveTrackUrlCompleted += OnSaveTrackUrlCompleted;
        }

        #endregion

        #region Nested type: AsyncState

        private class AsyncState : IDisposable
        {
            private readonly DataModel _parent;
            private readonly object _state;
            private readonly string _status;

            public AsyncState(DataModel parent, object state, string status)
            {
                _parent = parent;
                _state = state;
                _status = status;
                _parent.AddStatus(Status);
            }

            public object State
            {
                get { return _state; }
            }

            public string Status
            {
                get { return _status; }
            }

            #region IDisposable Members

            public void Dispose()
            {
                _parent.RemoveStatus(Status);
                GC.SuppressFinalize(this);
            }

            #endregion

            ~AsyncState()
            {
                Dispose();
            }
        }

        #endregion
    }
}